package com.pig.easy.bpm.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * todo:
 */
public class HttpClientUtils {

    private static final Logger log = LoggerFactory.getLogger(HttpClientUtils.class);

    private static RequestConfig requestConfig = null;
    private static final int connTimeout = 5000;
    private static final int readTimeout = 10000;
    private static final String URL = "url";
    private static final String METHOD = "method";
    private static final String HEADER_JSON = "headerJsonStr";
    private static final String BODY_JSON = "bodyJsonStr";
    private static final String PROCESS_VAR_MAP = "processVarMap";
    private static final String DEFAULT_CHARSET = "UTF-8";

    private volatile static HttpClientUtils instance = null;

    public static HttpClientUtils getInstance() {
        if (null == instance) {
            synchronized (HttpClientUtils.class) {
                if (null == instance) {
                    instance = new HttpClientUtils();
                }
            }
        }
        return instance;
    }


    public static String doMethod(String url, String method, String headerJsonStr, String bodyJsonStr, Map processVarMap) {

        ConcurrentHashMap<String, Object> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put(URL, url);
        concurrentHashMap.put(METHOD, method);
        concurrentHashMap.put(HEADER_JSON, headerJsonStr == null ? "" : headerJsonStr);
        concurrentHashMap.put(BODY_JSON, bodyJsonStr == null ? "" : bodyJsonStr);
        concurrentHashMap.put(PROCESS_VAR_MAP, processVarMap == null ? new HashMap<>() : processVarMap);
        String requestId = UUID.randomUUID().toString();

        if (log.isDebugEnabled()) {
            log.debug(" HttpClientUtils requestId{} start，method：{}，params：{}", requestId, "doMethod", JSONObject.toJSONString(concurrentHashMap));
        }

        String result = null;
        if (StringUtils.isEmpty(url)
                || StringUtils.isEmpty(method)) {
            log.error("HttpClientUtils requestId{} start，method：{}，result：{}", requestId, "doMethod", "url or method not allow null");
            throw new NullPointerException();
        }
        if ("GET".equals(method.toUpperCase())) {
            result = doGet(url, headerJsonStr, bodyJsonStr, processVarMap);

        }
        if ("POST".equals(method.toUpperCase())) {
            result = doPost(url, headerJsonStr, bodyJsonStr, processVarMap);
        }
        if (log.isDebugEnabled()) {
            log.debug(" HttpClientUtils requestId{} end，method：{}，result：{}", requestId, "doMethod", JSONObject.toJSONString(result));
        }
        return result;
    }

    public static String doGet(String url, String headerJsonStr, String bodyJsonStr, Map processVarMap) {
        String result = null;
        try {
            Map headerMap = replaceProcessVar(headerJsonStr, processVarMap);
            Map bodyMap = replaceProcessVar(bodyJsonStr, processVarMap);
            HttpResponse httpResponse = doGet(url, headerMap, bodyMap);
            if (httpResponse != null && httpResponse.getStatusLine() != null) {
                if (httpResponse.getEntity() != null) {
                    result = EntityUtils.toString(httpResponse.getEntity(), DEFAULT_CHARSET);
                }
            }
        } catch (Exception e) {
            log.error("HttpClientUtils doGet error {}", e);
            e.printStackTrace();
        }
        return result;
    }

    @Async
    public void doMethodAsync(String url, String method, String headerJsonStr, String bodyJsonStr, Map processVarMap) {
        doMethod(url, method, headerJsonStr, bodyJsonStr, processVarMap);
    }


    public static String doPost(String url, String headerJsonStr, String bodyJsonStr, Map processVarMap) {
        String result = null;
        try {
            Map headerMap = replaceProcessVar(headerJsonStr, processVarMap);
            Map bodyMap = replaceProcessVar(bodyJsonStr, processVarMap);

            HttpResponse httpResponse = doPost(url, headerMap, null, JSONObject.toJSONString(bodyMap));
            if (httpResponse != null && httpResponse.getStatusLine() != null) {
                if (httpResponse.getEntity() != null) {
                    result = EntityUtils.toString(httpResponse.getEntity(), DEFAULT_CHARSET);
                }
            }
        } catch (Exception e) {
            log.error("HttpClientUtils doPost error {}", e);
            e.printStackTrace();
        }
        return result;
    }

    public static HttpResponse doGet(String url,
                                     Map<String, Object> headers,
                                     Map<String, Object> querys)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpGet request = new HttpGet(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        return httpClient.execute(request);
    }

    /**
     * post form
     *
     * @param url
     * @param headers
     * @param querys
     * @param bodys
     * @return
     * @throws Exception
     */
    public HttpResponse doPost(String url,
                               Map<String, Object> headers,
                               Map<String, Object> querys,
                               Map<String, Object> bodys)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpPost request = new HttpPost(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        if (bodys != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key).toString()));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }

        return httpClient.execute(request);
    }

    /**
     * Post String
     *
     * @param url
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String url,
                                      Map<String, Object> headers,
                                      Map<String, Object> querys,
                                      String body)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpPost request = new HttpPost(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "application/json", "utf-8"));
        }
        return httpClient.execute(request);
    }

    /**
     * Post stream
     *
     * @param url
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public HttpResponse doPost(String url,
                               Map<String, Object> headers,
                               Map<String, Object> querys,
                               byte[] body)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpPost request = new HttpPost(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Put String
     *
     * @param url
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public HttpResponse doPut(String url,
                              Map<String, Object> headers,
                              Map<String, Object> querys,
                              String body)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpPut request = new HttpPut(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Put stream
     *
     * @param url
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public HttpResponse doPut(String url,
                              Map<String, Object> headers,
                              Map<String, Object> querys,
                              byte[] body)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpPut request = new HttpPut(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Delete
     *
     * @param url
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public HttpResponse doDelete(String url,
                                 Map<String, Object> headers,
                                 Map<String, Object> querys)
            throws Exception {
        HttpClient httpClient = wrapClient(url);

        HttpDelete request = new HttpDelete(buildUrl(url, querys));
        for (Map.Entry<String, Object> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue().toString());
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String url, Map<String, Object> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(url);

        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, Object> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !org.springframework.util.ObjectUtils.isEmpty(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!org.springframework.util.ObjectUtils.isEmpty(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue().toString(), DEFAULT_CHARSET));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) { //根据传来的URL 进行生产对应的Httpclient
            sslClient(httpClient);
        }
        // 设置请求和传输超时时间
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connTimeout);
        httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }

                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }


    /**
     * 判断字符串是否可以转化为json对象
     *
     * @param content
     * @return
     */
    public static boolean isJsonObject(String content) {
        if (StringUtils.isBlank(content))
            return false;
        try {
            JSONObject jsonStr = JSONObject.parseObject(content);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 判断字符串是否可以转化为JSON数组
     *
     * @param content
     * @return
     */
    public static boolean isJsonArray(String content) {
        if (StringUtils.isBlank(content))
            return false;
        StringUtils.isEmpty(content);
        try {
            JSONArray jsonStr = JSONArray.parseArray(content);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 功能描述: 将流程变量的值 替换掉 ${} 对应KEY的值
     *
     * @param jsonStr
     * @param processVarMap
     * @return :
     * @author : zhoulin.zhu
     * @date : 2020/3/19 9:59
     */
    private static Map replaceProcessVar(String jsonStr, Map processVarMap) {
        try {
            Map returnMap = JSONObject.parseObject(jsonStr, Map.class);
            if (returnMap == null) {
                returnMap = new HashMap();
            }
            for (Object map : returnMap.entrySet()) {
                String expression = ((Map.Entry) map).getValue().toString().replaceAll(" ", "");
                if (expression.startsWith("${")
                        && expression.endsWith("}")) {
                    expression = expression.substring(2, expression.length() - 1);
                    ((Map.Entry) map).setValue(processVarMap.getOrDefault(expression, ""));
                }
            }
            return returnMap;
        } catch (Exception e) {
            log.error("httpclientUtils metod replaceProcessVar error:{}", e);
            return new HashMap<String, String>();
        }
    }
}
